package marlon.sjf;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;

public class JavaCommandExecutor implements CommandExecutor {
	String S = System.getProperty("path.separator");
	
	@Override
	public int execute(Command cmd) throws SJFClientException {
		if (cmd.args.length <= 0) {
			printUsage();
			return 0;
		}
		Options opts = parseArgs(cmd.args);
		if (opts.exited)
			return 0;
		return runJava(opts, cmd);
	}
	
	private int runJava(Options opts, Command cmd) {
		String[] args = cmd.args;
		if (opts.argStartIndex < 0 || opts.argStartIndex >= args.length) {;
			printUsage();
			return 0;
		}
		for (String name: opts.props.stringPropertyNames()) {
			System.setProperty(name, opts.props.getProperty(name));
		}
		List<URL> classpath = new ArrayList<URL>();
		try {
			if (opts.jar) {
				classpath.add(new File(args[opts.argStartIndex]).getAbsoluteFile().toURI().toURL());
			}
			classpath.add(new File(cmd.cwd).toURI().toURL());		// add current path to classpath
			if (opts.classpath != null) {
				for (String path: opts.classpath.split(S)) {
					if (path.length() > 0)
						classpath.add(new File(path).getAbsoluteFile().toURI().toURL());
				}
			}
		} catch (MalformedURLException e) {
			e.printStackTrace(SJFServer.STDERR);	// ignore, impossible
		}

		try {
			URLClassLoader ucl = new URLClassLoader((URL[])classpath.toArray(new URL[classpath.size()]));
			String mainClass = null;
			int argIndex = opts.argStartIndex;
			if (opts.jar) {
				mainClass = getJarMainClass(cmd.args[argIndex++]);
			} else {
				mainClass = cmd.args[argIndex++];
			}
			Class<?> clazz = ucl.loadClass(mainClass);
			String[] javaArgs = new String[args.length-argIndex];
			System.arraycopy(args, argIndex, javaArgs, 0, javaArgs.length);
			Method method = clazz.getDeclaredMethod("main", String[].class);
			method.invoke(null, (Object)javaArgs);
			return 0;
		} catch (IOException e) {
			throw new SJFClientException(e);
		} catch (ClassNotFoundException e) {
			throw new SJFClientException(e);
		} catch (NoSuchMethodException e) {
			throw new SJFClientException(e);
		} catch (IllegalAccessException e) {
			throw new SJFClientException(e);
		} catch (InvocationTargetException e) {
			throw new SJFClientException(e.getTargetException());
		}
	}
	
	private String getJarMainClass(String jar) throws IOException {
		JarInputStream jarIn = new JarInputStream(new FileInputStream(new File(jar).getAbsoluteFile()));
		Manifest manifest = jarIn.getManifest();
		return manifest.getMainAttributes().getValue("Main-Class");
	}
	
	private Options parseArgs(String[] args) {
		Options opts = new Options();
		try {
			for (int i = 0; i < args.length; i++) {
				if (args[i].equals("-version") || args[i].equals("-showversion")) {
					System.out.println(System.getProperty("java.version") + " " + 
							System.getProperty("java.vendor"));
					if (args[i].equals("-version")) {
						opts.exited = true;
						break;
					}
				} else if (args[i].equals("-cp") || args[i].equals("-classpath")) {
					opts.classpath = args[++i];
				} else if (args[i].startsWith("-D")) {
					String[] pair = args[i].substring(2).split("=", 2);
					opts.props.put(pair[0], pair[1]);
				} else if (args[i].equals("-?") || args[i].equals("-help")) {
					printUsage();
					opts.exited = true;
					break;
				} else if (args[i].equals("-jar")) {
					opts.jar = true;
					opts.argStartIndex = ++i;
					break;
				} else if (args[i].startsWith("-")) {
					System.err.println("unknown option " + args[i]);
					printUsage();
					opts.exited = true;
					break;
				} else {
					opts.argStartIndex = i;
					break;
				}
			}
		} catch (Exception e) {
			printUsage();
			opts.exited = true;
		}
		return opts;
	}
	
	private void printUsage() {
		System.err.print(
			"Usage: java [-options] class [args...]\n" +
			"            (to execute a class)\n" +
			"    or java [-options] -jar jarfile [args...]\n" +
		  	"            (to execute a jar file)\n" +
		  	"where options include:\n" +
		  	"    -cp <class search path of directories and zip/jar files>\n" +
		  	"    -classpath <class search path of directories and zip/jar files>\n" +
		  	"                  A " + S +" separated list of directories, JAR archives,\n" +
		  	"                  and ZIP archives to search class files.\n" +
		  	"    -D<name>=<value>\n" +
		  	"                  set a system property\n" +
		  	"    -version      print product version and exit\n" +
		  	"    -showversion  print product version and continue\n" +
		  	"    -? -help      print this help message\n"
		);
	}
	
	static class Options {
		boolean version;
		String classpath;
		boolean jar;
		Properties props = new Properties();
		boolean exited;
		int argStartIndex = -1;
	}
}
